/*
 * CubePuzzle - A 3D puzzle game
 * Copyright (C) 2010  V.Vamsi Krishna
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "puzzle.h"
#include "GLButton.h"
#include "GLMessageBox.h"
#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>
#define TIME_INTERVAL 50

Puzzle *g_Puzzle = NULL;
GLfloat angles[2] = {0, 30};
//text objects for various strings on the hud.
char g_TimerTextData[256];
TextObject *g_TimerText = NULL;
TextObject *g_MessageText = NULL;
Uint32 g_NumTicks = 0;
Uint32 g_time = 0;


GLButton *but; 
GLButton *helpButton;

bool g_GameOver = false;

//textures for the mouse pointer
Texture *cursor[2] = {NULL, NULL};

struct Pt
{
    int x;
    int y;
};

static struct Pt start, end;

Uint32 timerFunc( Uint32 tmp)
{
    if(GLRenderer::updateAngle())
    {
        //glutTimerFunc(100, timerFunc, 0);
        return TIME_INTERVAL;
    }
    else if(GLRenderer::getIndex() > -1)
    {
        g_Puzzle->rotLayer(GLRenderer::getIndex(), false);
        GLRenderer::setIndex(-1);      
    }
    return 0;
}

void mot(int x, int y)
{
    int xdiff = x - start.x;
    int ydiff = y - start.y; 
    angles[0] += xdiff; //10.0;
    angles[1] += ydiff;//10.0;
    start.x = x;
    start.y = y;
}

void mouse(int state, int x, int y)
{
        start.x = x;
        start.y = y;
}
void Quit(int val)
{
	SDL_SetTimer(TIME_INTERVAL, NULL);
    //free all the memorey
    delete g_MenuFont;
    delete g_HudFont;
    delete g_TimerText;
    delete g_MessageText;
    delete cursor[0];
    delete cursor[1];
    delete but;
    delete helpButton;
    //quit sdl
	SDL_Quit();
	exit(val);
}

void InitGame();
Uint32 animFinised(Uint32 tmp)
{
    GLRenderer::set2DProjection(WIDTH, HEIGHT);
    char buff[100];
    memset(buff, 0, 100 * sizeof(char));
    sprintf(buff, "Congratulations you Solved the Puzzle in %d Sec", g_time); 
    int retval = GLMessageBox::information((char*)"Game Over", (char*)buff, (char*)"QUIT", (char*)"NEW GAME");
    if(retval == 0)
        Quit(0);
    else
        printf("start New Game\n");
        InitGame();
    return 0;
}


void kbfunc(SDLKey key)
{
	unsigned int indx = (unsigned int) (key - SDLK_0);
	if(indx < g_Puzzle->getNumLayers())
	{
        if(GLRenderer::getIndex() < 0)
        {
            GLRenderer::setIndex(indx);
            SDL_SetTimer(TIME_INTERVAL, timerFunc);
        }
		//p.rotLayer(indx);
	}
    switch(key)
    {
        case SDLK_SPACE:
//		    std::cout << (p.isSolved()? "Done" : "No") << std::endl;
            break;

        case SDLK_F1:
        {
            g_EnableGrid = !g_EnableGrid;
            break;
        }
        case SDLK_ESCAPE:
            Quit(0);
	}   
}

void pickLayer(int x, int y)
{
    float z;
    double objx, objy, objz;
	
    //Read the window z co-ordinate 
    //(the z value on that point in unit cube)		
    glReadPixels( x, viewport[3]-y, 1, 1,
		 GL_DEPTH_COMPONENT, GL_FLOAT, &z);

    //Unproject the window co-ordinates to 
    //find the world co-ordinates.
    gluUnProject( x, viewport[3]-y, z, modelview, 
		projection, viewport, &objx, &objy, &objz );
    Uint32 indx = ((int)objy)/GLRenderer::layer_gap; //obtain the layer number based on the y-coord value.
    if(indx < g_Puzzle->getNumLayers())
	{
        if(GLRenderer::getIndex() < 0)
        {
            GLRenderer::setIndex(indx);
            SDL_SetTimer(TIME_INTERVAL, timerFunc);
        }
	}
}

//handling help button press event
void helpPressed()
{
    GLRenderer::set2DProjection(WIDTH, HEIGHT);
    int ret = GLMessageBox::information((char*)"Game Paused", 
        (char*)"Rotate layers of cubes so that none of the cubes overlap. Click on a cube to rotate the corresponding layer of cubes.");
}

//handling button press events
void buttonPressed()
{
    //printf("Button Pressed\n");
    GLRenderer::set2DProjection(WIDTH, HEIGHT);
    int ret = GLMessageBox::information((char*)"Game Paused", (char*)"CubePuzzle is Paused. Press resume to continue",
                                             (char*)"QUIT", (char*)"NEW GAME", (char*)"RESUME");
    switch(ret)
    {
        case 0:
            //quit clicked
            Quit(0);
            break;
        case 1:
            //reset Game
            InitGame();
            break;
        case 2:
        default://do nothing for resume
            break;
    }
}

/* Function to be used by the ui elements to get mouse inputs*/
void inputHandler(int &x, int &y, bool &clicked)
{
    SDL_PumpEvents();
    Uint8 *keystate = SDL_GetKeyState(NULL);
    if(keystate[SDLK_ESCAPE])
    {
        return;
        //Quit(0);
    }
    if(SDL_GetMouseState(&x, &y)&SDL_BUTTON(1))
    {
        clicked = true;
    }
    else
        clicked = false;
    y = HEIGHT - y;
}

void drawHud(int mx, int my, float z)
{
    glDisable(GL_DEPTH_TEST);
    g_TimerText->drawText(10, HEIGHT);
    but->show();
    helpButton->show();
    glDisable(GL_TEXTURE_2D);
    glColor4f(.5, .5, .5, 1);
    int x = 0, y = 50;
    int verts[] = {x, y-y, x, y, x+WIDTH, y, x+WIDTH, y-y}; 
    float tex[] = {0, 1, 0, 0, 1, 0, 1, 1};

    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(2, GL_INT, 0, verts);
    glDrawArrays(GL_QUADS, 0, 4);
    glDisableClientState(GL_VERTEX_ARRAY);
    
    glEnable(GL_TEXTURE_2D); 

    g_MessageText->setColor(255, 255,255);
    g_MessageText->drawText(WIDTH/2.0f-g_MessageText->getW()/2.0f, 40);

    glColor4f(1, 1, 1, 1);
    GLui::drawCursor(mx, my, cursor[(z<1.0? 1:0)]->GetTexID());
    glEnable(GL_DEPTH_TEST);
}

void display(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	GLRenderer::setCamera();//to be called everytime puzzle changes
    if(g_GameOver)
    {
        glColor4f(.5, 1, .2, 1);
    }
    else
    {
        glColor4f(.2, .5, 1, 1);
    }
        
	g_Puzzle->render(angles);

    //////////// MOVING THINGS HERE FROM HUD SO THAT WE HAVE ACCESS TO 3D SETUP.    
    int x, y;
    float z;
    SDL_GetMouseState(&x, &y);
    y = HEIGHT-y;

    glFlush(); //force drawing before reading z;
    glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &z);

    ////////////////////////////////////////
    //set2D projection and draw required stuff
    GLRenderer::set2DProjection(WIDTH, HEIGHT);
    drawHud(x, y, z);

    //reset the 3D projection
    GLRenderer::set3DProjection(WIDTH, HEIGHT);
    SDL_GL_SwapBuffers();
}

void InitGame()
{
    static int retVal = 0;
    if(!g_Puzzle)
    {
        GLRenderer::set2DProjection(WIDTH, HEIGHT);
        retVal = GLMessageBox::information("Cube Puzzle", "Select difficulty of Game", "EASY", "NORMAL", "HARD");  
    }
    else
    {
        delete g_Puzzle;
    }
    int pSize = 4;
    int pLayers = 4;
    switch(retVal)
    {
        case 0:
		    pSize =  2 + (int) ((1.0 * 2) * (rand() / (RAND_MAX + 1.0)));
            break;
        case 1:
            pSize = 4;
		    pLayers =  4 + (int) ((1.0 * 2) * (rand() / (RAND_MAX + 1.0)));
            break;
        case 2:
		    pSize =  6; 
		    pLayers =  4 + (int) ((1.0 * 4) * (rand() / (RAND_MAX + 1.0)));
            break;
        case -1:
            Quit(0);
    }
    g_Puzzle = new Puzzle(pSize, pLayers);
    GLRenderer::setEyeAndCenter(g_Puzzle->getSize(), g_Puzzle->getNumLayers());
    g_GameOver = false;
    g_time = 0;
    g_MessageText->setText("Press F1 for help, Esc to quit game");
}
int main(int argc, char *argv[])
{

	int VideoFlags = 0;
	SDL_Surface *MainWindow;

	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0)	// try to initialize SDL video module
	{
		printf("SDL ERROR:%s\n", SDL_GetError());	// report error if it fails
		Quit(0);
	}
	const SDL_VideoInfo *VideoInfo = SDL_GetVideoInfo();	// query SDL for information about our video hardware

	if (VideoInfo == NULL)	// if this query fails
	{
		printf("Failed getting Video Info : %s\n", SDL_GetError());	// report error
		Quit(0);
	}

	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);	// tell SDL that the GL drawing is going to be double buffered

	//VideoFlags = SDL_OPENGL;

    MainWindow = SDL_SetVideoMode(WIDTH, HEIGHT, 32, SDL_OPENGL | SDL_HWPALETTE );

	if (MainWindow == NULL)	// if window creation failed
	{
		printf("Failed to Create Window : %s\n", SDL_GetError());	// report error
		Quit(0);
	}
	SDL_WM_SetCaption("Cube Puzzle", "Cube Puzzle");
    SDL_ShowCursor(false);

	GLRenderer::init(WIDTH, HEIGHT);
    GLRenderer::resize(WIDTH, HEIGHT);
//    GLRenderer::setEyeAndCenter(g_Puzzle->getSize(), g_Puzzle->getNumLayers());

    //load the mouse textures
    cursor[0] = new Texture("images/blue.tga");
    cursor[1] = new Texture("images/green.tga");
    cursor[0]->Load();
    cursor[1]->Load();

    //initializing glUi related things.
    g_GLuiFont = g_HudFont;//new TTFFont("fonts/accid___.ttf", 30); //g_HudFont;
    g_GLuiCursorTex = cursor[0];//->GetTexID();

    but = new GLButton(WIDTH-100, HEIGHT, inputHandler, buttonPressed, "PAUSE");
    helpButton = new GLButton(WIDTH-100, HEIGHT - but->getHeight() - but->getHeight()/4.0, inputHandler, helpPressed, "HELP");

    GLMessageBox::setCallbacks(inputHandler, SDL_GL_SwapBuffers);   
    
    //allocate memory for the text Objects
    g_TimerText = new TextObject(g_HudFont);
    g_TimerText->setColor(255, 255, 255); 
    g_TimerText->setText("Timer: 0sec");
    
    g_MessageText = new TextObject(g_HudFont);
    g_MessageText->setColor(255, 255, 255);

    //initialize the game values;
    InitGame();

    SDL_SetTimer(TIME_INTERVAL, timerFunc);
    SDL_Event evnt;
    bool tracking = false; //to see if we should start rotating the puzzle
    bool mouseMoved = true; //to see if user just clicked or moved.
    
    while(1)
    {
    //update the hud data
        Uint32 curVal = SDL_GetTicks();

        /// final animation rendering.. 
        if(g_GameOver)
        {
        angles[0] += .1;
        if(angles[0] > 180)
            animFinised(0);
        }
 
        if(curVal-g_NumTicks > 1000)
        {
           //if(!p.isSolved())
           if(!g_Puzzle->isSolved())
            {
                memset(g_TimerTextData, 0, sizeof(char)*256);
                sprintf(g_TimerTextData, "Time: %dsec", g_time++);
                g_TimerText->setText(g_TimerTextData);
            }
            g_NumTicks = curVal;
        }

        if(g_Puzzle->isSolved())
        {
            //game is complete put out a message and start playing animation
            g_MessageText->setText("Puzzle is Solved!!");
            if(!g_GameOver)
            {
                g_GameOver = true;
                angles[0] = 0;
                angles[1] = 30;
            }
        }
   
        display();
        while(SDL_PollEvent(&evnt))
        {
            if(!g_GameOver)
            {
                switch(evnt.type)
                {
                    case SDL_MOUSEBUTTONDOWN:
                        mouse(evnt.button.state, evnt.button.x, evnt.button.y);
                        mouseMoved = false;
                        tracking = true;
                        break;
                    case SDL_MOUSEBUTTONUP:
                        tracking = false;
                        if(!mouseMoved)
                        {//try picking the object user clicked.
                            pickLayer(evnt.button.x, evnt.button.y);
                        }                    
                        break;
                    case SDL_MOUSEMOTION:
                        if(tracking)
                        {
                            mot(evnt.button.x, evnt.button.y);
                        }
                        mouseMoved = true;
                        break;
                }
            }
            //these events are required even if the game is over
            if(evnt.type == SDL_KEYUP)
                kbfunc(evnt.key.keysym.sym);
            if(evnt.type == SDL_QUIT)
                Quit(0);

        }
    }
    return 0;
}
